#include <pthread.h>
#include <unistd.h>

#include <cassert>
#include <iostream>
#include <queue>


class Producer {
private: 
  static void * callbackToThis(void * arg) {
    Producer * pThis = static_cast<Producer*>(arg);
    assert(pThis != nullptr);

    pThis->work();

    return nullptr;
  }

public:
  Producer(volatile bool & isRunning, std::queue<int> & queue
			, pthread_mutex_t & mutex, pthread_cond_t & cond) 
  : isRunning_(isRunning)
	, queue_(queue)
	, mutex_(mutex)
	, cond_(cond)
  {
  }

  void start(){
    assert(0 == pthread_create(&tid_, NULL, callbackToThis, this));
  }

  void join(){
    assert(0 == pthread_join(tid_, NULL));
  }

private:
  void work(){
		int counter = 0;

    while(isRunning_){
      printf("Producer is producing\n"); 

			assert(0 == pthread_mutex_lock(&mutex_)); 
			{
				if(queue_.empty()){
					assert(0 == pthread_cond_signal(&cond_));
				}

				queue_.push(counter++);

			}
			assert(0 == pthread_mutex_unlock(&mutex_));

			//do other work...
			usleep(100);
    }//while

    printf("queue-size(): %lu, Producer exit\n", queue_.size());
  }

private:
  pthread_t tid_;   
  volatile bool & isRunning_; 
	std::queue<int> & queue_;
	pthread_mutex_t & mutex_;	
	pthread_cond_t & cond_;
};


class Consumer {

  static void * callbackToThis(void * arg) {
    Consumer * pThis = static_cast<Consumer*>(arg);
    assert(pThis != nullptr);

    pThis->work();

    return nullptr;
  }
public:
  Consumer(volatile bool & isRunning, std::queue<int> & queue
			, pthread_mutex_t & mutex, pthread_cond_t & cond) 
  : isRunning_(isRunning)
	, queue_(queue)
	, mutex_(mutex)
	, cond_(cond)
  {
  }

  void start(){
    assert(0 == pthread_create(&tid_, NULL, callbackToThis, this));
  }

  void join(){
    assert(0 == pthread_join(tid_, NULL));
  }
private:
  void work(){
    while(isRunning_){
      printf("Consumer is consuming\n"); 
			int counter = -1;
			assert(0 == pthread_mutex_lock(&mutex_)); 
			{
				while(queue_.empty() && isRunning_) 
					assert(0 == pthread_cond_wait(&cond_, &mutex_));
				if(!queue_.empty()){
					counter = queue_.front();
					queue_.pop();
				}
			}
			assert(0 == pthread_mutex_unlock(&mutex_));
			printf("consume counter value:(%d) from queue", counter);
		}
    printf("queue-size(): %lu, Consumer exit\n", queue_.size());
	}
private:
  pthread_t tid_;   
  volatile bool & isRunning_; 
	std::queue<int> & queue_;
	pthread_mutex_t & mutex_;	
	pthread_cond_t & cond_;
};
class Application {
public:
  Application(){
		assert(0 == pthread_mutex_init(&mutex_, NULL));
		assert(0 == pthread_cond_init(&cond_, NULL));
  }
  ~Application(){

		assert(0 == pthread_cond_destroy(&cond_));
		assert(0 == pthread_mutex_destroy(&mutex_));

    printf("App exit\n");
  }

  void run(){
    Producer producer(isRunning_, queue_, mutex_, cond_);
    Consumer consumer(isRunning_, queue_, mutex_, cond_);

    producer.start();
    consumer.start();

    //sleeping for producer & consumer working sleep(1);
    usleep(10*1000); 

		stop();

    //wait all sub-thread to exit
    consumer.join();
    producer.join();
  }

	void stop(){
		if(isRunning_ == true){
			isRunning_ = false;
			pthread_cond_signal(&cond_);
		}
	}

private:
  volatile bool isRunning_ = true;
	std::queue<int> queue_;
	pthread_mutex_t mutex_;	
	pthread_cond_t cond_;
};

int main(){

  Application app;
  app.run();

  return 0;
}
